const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');

/**
 * Launches Minecraft with the specified parameters
 * @param {string} mcPath - Path to .minecraft directory
 * @param {string} versionName - Minecraft version name
 * @param {string} playerName - Player username
 * @param {Object} options - Launch options
 * @param {string} options.javaPath - Path to Java executable
 * @param {number} options.memory - Memory allocation in MB
 * @param {boolean} options.useSystemMemory - Whether to use system memory detection
 */
async function launchMinecraft(mcPath, versionName, playerName, options) {
    try {
        // Normalize path
        if (!mcPath.endsWith(path.sep)) {
            mcPath += path.sep;
        }

        // Read version JSON file
        const versionJsonPath = path.join(mcPath, 'versions', versionName, `${versionName}.json`);
        const versionJson = await readJsonFile(versionJsonPath);

        // Get main class
        const mainClass = versionJson.mainClass;

        // Build libraries path
        const libraries = await buildLibrariesPath(mcPath, versionJson);

        // Build game arguments
        const gameArgs = buildGameArguments(mcPath, versionName, playerName, versionJson);

        // Build Java command
        const javaCommand = buildJavaCommand(mcPath, versionName, mainClass, libraries, gameArgs, options);

        // Execute command
        console.log('Launching Minecraft with command:', javaCommand);
        const child = exec(javaCommand, { windowsHide: false });

        child.stdout.on('data', (data) => {
            process.stdout.write(`${data}`);
        });

        child.stderr.on('data', (data) => {
            console.error(`stderr: ${data}`);
        });

        child.on('close', (code) => {
            console.log(`Minecraft process exited with code ${code}`);
        });

        return child;

    } catch (error) {
        console.error('Error launching Minecraft:', error);
        throw error;
    }
}

/**
 * Builds the libraries classpath
 * @param {string} mcPath - Path to .minecraft directory
 * @param {Object} versionJson - Version JSON object
 */
async function buildLibrariesPath(mcPath, versionJson) {
    // Start with the version jar
    let result = [path.join(mcPath, 'versions', versionJson.id, `${versionJson.id}.jar`)];

    // Add all libraries
    if (versionJson.libraries && Array.isArray(versionJson.libraries)) {
        for (const lib of versionJson.libraries) {
            // Check library rules
            if (!checkLibraryRules(lib)) {
                continue;
            }

            // Get library path
            const libPath = getLibraryPath(mcPath, lib);
            if (libPath) {
                result.push(libPath);
            }
        }
    }

    return result.join(path.delimiter);
}

/**
 * Checks if a library should be included based on rules
 * @param {Object} lib - Library object
 */
function checkLibraryRules(lib) {
    // If no rules, always include
    if (!lib.rules || lib.rules.length === 0) {
        return true;
    }

    const osName = 'windows';
    const osArch = process.arch === 'x64' ? 'x86_64' : 'x86';

    let shouldInclude = true;

    for (const rule of lib.rules) {
        if (rule.action === 'allow') {
            // If no OS specified, allow
            if (!rule.os) {
                shouldInclude = true;
                continue;
            }

            // Check OS condition
            if (rule.os.name === osName) {
                // Check arch if specified
                if (rule.os.arch) {
                    shouldInclude = (rule.os.arch === osArch);
                } else {
                    shouldInclude = true;
                }
            } else {
                shouldInclude = false;
            }
        } else if (rule.action === 'disallow') {
            // If no OS specified, disallow
            if (!rule.os) {
                shouldInclude = false;
                continue;
            }

            // Check OS condition
            if (rule.os.name === osName) {
                shouldInclude = false;
            }
        }
    }

    return shouldInclude;
}

/**
 * Gets the path to a library file
 * @param {string} mcPath - Path to .minecraft directory
 * @param {Object} lib - Library object
 */
function getLibraryPath(mcPath, lib) {
    try {
        const parts = lib.name.split(':');
        const groupPath = parts[0].replace(/\./g, path.sep);
        const artifactId = parts[1];
        const version = parts[2];

        // Base path
        const basePath = path.join(mcPath, 'libraries', groupPath, artifactId, version);
        const baseFile = `${artifactId}-${version}`;

        // Check for natives
        if (lib.natives && lib.natives.windows) {
            const classifier = lib.natives.windows.replace('${arch}', process.arch === 'x64' ? '64' : '32');
            const nativePath = path.join(basePath, `${baseFile}-${classifier}.jar`);

            if (fs.existsSync(nativePath)) {
                return nativePath;
            }
        }

        // Default to regular jar
        const jarPath = path.join(basePath, `${baseFile}.jar`);
        if (fs.existsSync(jarPath)) {
            return jarPath;
        }

        return '';
    } catch (error) {
        console.error('Error getting library path:', error);
        return '';
    }
}

/**
 * Builds the game arguments string
 * @param {string} mcPath - Path to .minecraft directory
 * @param {string} versionName - Minecraft version name
 * @param {string} playerName - Player username
 * @param {Object} versionJson - Version JSON object
 */
function buildGameArguments(mcPath, versionName, playerName, versionJson) {
    const assetsPath = path.join(mcPath, 'assets');
    const assetsIndex = versionJson.assets || '';

    let args = '';

    // Handle older versions with minecraftArguments
    if (versionJson.minecraftArguments) {
        args = versionJson.minecraftArguments;
    }

    // Handle newer versions with arguments.game
    if (versionJson.arguments && versionJson.arguments.game) {
        for (const arg of versionJson.arguments.game) {
            if (typeof arg === 'string') {
                args += ' ' + arg;
            }
        }
    }

    // Replace placeholders
    args = args.replace(/\$\{auth_player_name\}/g, playerName);
    args = args.replace(/\$\{version_name\}/g, versionName);
    args = args.replace(/\$\{game_directory\}/g, mcPath);
    args = args.replace(/\$\{assets_root\}/g, assetsPath);
    args = args.replace(/\$\{assets_index_name\}/g, assetsIndex);
    args = args.replace(/\$\{auth_uuid\}/g, '00000000-0000-0000-0000-000000000000');
    args = args.replace(/\$\{auth_access_token\}/g, '00000000000000000000000000000000');
    args = args.replace(/\$\{user_type\}/g, 'legacy');
    args = args.replace(/\$\{version_type\}/g, `"WMML 0.1.26"`);

    return args.trim();
}

/**
 * Builds the complete Java command
 * @param {string} mcPath - Path to .minecraft directory
 * @param {string} versionName - Minecraft version name
 * @param {string} mainClass - Main class to launch
 * @param {string} libraries - Classpath string
 * @param {string} gameArgs - Game arguments string
 * @param {Object} options - Launch options
 */
function buildJavaCommand(mcPath, versionName, mainClass, libraries, gameArgs, options) {
    const { javaPath, memory, useSystemMemory } = options;
    
    // Memory settings
    let memorySettings = '';
    if (!useSystemMemory && memory) {
        memorySettings = `-Xmx${memory}M -Xms${memory}M `;
    }

    // Common JVM arguments
    const commonArgs = [
        '-Dfile.encoding=GB18030',
        '-Dsun.stdout.encoding=GB18030',
        '-Dsun.stderr.encoding=GB18030',
        '-Djava.rmi.server.useCodebaseOnly=true',
        '-Dcom.sun.jndi.rmi.object.trustURLCodebase=false',
        '-Dcom.sun.jndi.cosnaming.object.trustURLCodebase=false',
        '-Dlog4j2.formatMsgNoLookups=true',
        `-Dlog4j.configurationFile=${path.join(mcPath, 'versions', versionName, 'log4j2.xml')}`,
        `-Dminecraft.client.jar=${path.join(mcPath, 'versions', versionName, `${versionName}.jar`)}`,
        '-XX:+UnlockExperimentalVMOptions',
        '-XX:+UseG1GC',
        '-XX:G1NewSizePercent=20',
        '-XX:G1ReservePercent=20',
        '-XX:MaxGCPauseMillis=50',
        '-XX:G1HeapRegionSize=32m',
        '-XX:-UseAdaptiveSizePolicy',
        '-XX:-OmitStackTraceInFastThrow',
        '-XX:-DontCompileHugeMethods',
        '-Dfml.ignoreInvalidMinecraftCertificates=true',
        '-Dfml.ignorePatchDiscrepancies=true',
        '-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump',
        `-Djava.library.path=${path.join(mcPath, 'versions', versionName, 'natives-windows-x86_64')}`,
        `-Djna.tmpdir=${path.join(mcPath, 'versions', versionName, 'natives-windows-x86_64')}`,
        `-Dorg.lwjgl.system.SharedLibraryExtractPath=${path.join(mcPath, 'versions', versionName, 'natives-windows-x86_64')}`,
        `-Dio.netty.native.workdir=${path.join(mcPath, 'versions', versionName, 'natives-windows-x86_64')}`,
        '-Dminecraft.launcher.brand=WMML',
        `-Dminecraft.launcher.version=0.1.26`
    ].join(' ');

    // Construct full command
    return `cmd /K ${javaPath} ${memorySettings}${commonArgs} -cp "${libraries}" ${mainClass} ${gameArgs}`;
}

/**
 * Reads a JSON file and parses it
 * @param {string} filePath - Path to JSON file
 */
async function readJsonFile(filePath) {
    try {
        const content = await fs.promises.readFile(filePath, 'utf-8');
        return JSON.parse(content);
    } catch (error) {
        console.error(`Error reading JSON file ${filePath}:`, error);
        throw error;
    }
}


const mcPath = '.minecraft';
const versionName = '1.20.1';
const playerName = 'Player123';

const options = {
    javaPath: 'java',
    memory: 4096,
    useSystemMemory: false
};

launchMinecraft(mcPath, versionName, playerName, options)
    .then(process => {
        console.log('Minecraft launched with PID:', process.pid);
    })
    .catch(err => {
        console.error('Failed to launch Minecraft:', err);
    });